/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package GameGL;

import AdvanceMath.Vector2;
import Draw.ImageCollection;
import Input.InputAdvance;
import Input.KeyBoard;
import Input.Mouse;
import Render.DefaultRenderer;
import Render.Renderer;
import Utilities.ViewScreen;
import java.awt.*;
import java.awt.event.*;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

/*
 * Credits for inspiration on how to properly implement differnt methods and states:
 * Fullscreen class: http://www.felixgers.de/teaching/jogl/fullscreen.html
 */

/**
 * 
 * @author RomulusAaron
 */
public abstract class GameGL implements GLEventListener, Runnable {
    //User Input items
    public KeyBoard keyboard;
    public Mouse mouse;
    protected Input input;
    //Base Game Logic and Items
    private GameBase base;
    public Frame gameFrame;
    private Thread gameThread;
    protected Vector2 ScreenSize;
    //Graphics
    public Renderer renderer;
    protected GL gl;
    public Graphics g;
    public ImageCollection batch;
    //Fullscreen
    private boolean inFullScreen=false;
    private boolean hasBeenInFullScreen=false;
    
    public GameGL(){
        keyboard=new KeyBoard();
        mouse=Mouse.getCurrentInstance();
        renderer=new DefaultRenderer();
        gameThread=new Thread(this);
        input=new Input();
        ScreenSize=new Vector2(800,600);
        inFullScreen=false;
        hasBeenInFullScreen = false;
        batch= new ImageCollection(new ViewScreen());
    }
    
    public abstract void InitializeAndLoad();
    public abstract void Update();
    public abstract void Render(GL gl, ImageCollection batch);
    
    public void start(){
        InitializeAndLoad();
        gameThread.start();
    }
    
    public void setGameFrame(Frame gameFrame){
        this.gameFrame=gameFrame;
    }
    public void setGameBase(GameBase base){
        this.base=base;
    }
    public void setGraphics(Graphics g){
        this.g=g;
    }
    public boolean isFullScreen(){
        return inFullScreen;
    }
    public boolean hasBeenInFullScreen(){
        return hasBeenInFullScreen;
    }
    /**
     * Enters fullscreen. Note, Because of compatability issues with Win32 systems, Java has trouble with
     * fullscreen. Thus, once full screen has been entered this instance, it cannot be re-entered.
     * Once fullscreen has been exited, it cannot be re-entered without recreating the entire GameGL class.
     */
    public void enterFullScreen(){
        if(!inFullScreen && gameFrame!=null){
            GraphicsDevice gd= GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
            try{
                if(gd.isFullScreenSupported() && !hasBeenInFullScreen){
                    gameFrame.dispose();
                    gameFrame.setUndecorated(true);
                    gd.setFullScreenWindow(gameFrame);
                    inFullScreen=true;
                    gameFrame.pack();
                    gameFrame.setVisible(true);
                    hasBeenInFullScreen=true;
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    /**
     * Exits fullscreen. Note, Because of compatability issues with Win32 systems, Java has trouble with
     * fullscreen. Thus, once full screen has been entered this instance, it cannot be re-entered.
     * Once fullscreen has been exited, it cannot be re-entered without recreating the entire GameGL class.
     */
    public void exitFullScreen(){
        if(inFullScreen && gameFrame!=null){
            GraphicsDevice gd= GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
            try{
                if(gd.isFullScreenSupported()){
                    gameFrame.dispose();
                    gameFrame.setUndecorated(false);
                    gd.setFullScreenWindow(null);
                    inFullScreen=false;
                    gameFrame.setSize((int)ScreenSize.getX(), (int)ScreenSize.getY());
                    gameFrame.setVisible(true);
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    /**
     * Causes the game to quit and the program to exit.
     */
    public void Quit(){
        base.Quit();
    }
    
    /**
     * The default init for when the GameGL is called as a Render. By default,
     * the ShadeModel is set as GL_SMOOTH).
     * @param drawable 
     */
    public void init(GLAutoDrawable drawable){
        gl=drawable.getGL();

        // Setup the drawing area and shading mode
        gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
        gl.glShadeModel(GL.GL_SMOOTH); // try setting this to GL_FLAT and see what happens.
    }

    /**
     * Displays what is asked to be drawn. It will call glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT),
     * then call Render(GL gl) as implemented by an implementing class, and then call glFlush(), which pushes
     * the drawing methods to the computer's implementation of OpenGL.
     * @param drawable 
     */
    public void display(GLAutoDrawable drawable){
//        renderer.display(drawable);
        GL gl = drawable.getGL();

        // Clear the drawing area
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        Render(gl, batch);
        gl.glFlush();
        drawable.getGL().glFlush();
        batch.Render(g, gameFrame);
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height){
        GL gl = drawable.getGL();
        GLU glu = new GLU();

        if (height <= 0) { // avoid a divide by zero error!
        
            height = 1;
        }
        final float h = (float) width / (float) height;
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(45.0f, h, 1.0, 20.0);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged){
        
    }
    
    /**
     * Calls the function setSwapInterfal which is supposed to enable VSync. 
     * However, it is not a sure thing to have an impact.
     * @param set -True or False
     */
    public void setVSync(boolean set){
        if(set){
            gl.setSwapInterval(1);
        }else{
            gl.setSwapInterval(0);
        }
    }
    
    public void run() {
        synchronized (this) {
            while (true) {
                try {
                    this.Update();
                    this.wait(16);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    
    public class Input extends InputAdvance{

        @Override
        public void keyTyped(KeyEvent e) {
            
        }

        @Override
        public void keyPressed(KeyEvent e) {
            keyboard.setPressed(e);
        }

        @Override
        public void keyReleased(KeyEvent e) {
            keyboard.setRelease(e);
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            
        }

        @Override
        public void mousePressed(MouseEvent e) {
            mouse.setButton(e);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            mouse.setRelease(e);
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            mouse.mouseMoved(e);
        }

        @Override
        public void mouseExited(MouseEvent e) {
            mouse.mouseMoved(e);
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            mouse.mouseMoved(e);
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            mouse.mouseMoved(e);
        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            mouse.mouseScroll(e);
        }
        
    }
}
